#include <jni.h>
#include <string>
#include <android/native_window_jni.h>
#include <android/native_window.h>
#include <unistd.h>
#include <pthread.h>

extern "C" {
#include "libavformat/avformat.h"
#include "libavutil/imgutils.h"
#include "libavutil/time.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
}

#include "DataQueue.h"
#include "log.h"




AVFormatContext *avFormatContext;
AVCodecContext *vCodecContext;
AVCodecContext *aCodecContext;
jmethodID onSizeChange;
jmethodID playTrack;
int videoIndex = -1;
int audioIndex = -1;
bool isStart = false;

DataQueue *videoQueue;
DataQueue *audioQueue;

ANativeWindow *nativeWindow;
ANativeWindow_Buffer windowBuffer;

uint8_t *outBuffer;
AVFrame *rgbFrame;
SwsContext *swsContext;
int width;
int height;
jobject mInstance;

_JavaVM *javaVm = NULL;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *unused) {
    jint result = -1;
    javaVm = vm;
    JNIEnv *env;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
        return result;
    }
    return JNI_VERSION_1_4;
}

void *decodePacket(void *pVoid) {
    LogI("----------decode packet thread");
    while (isStart) {
        AVPacket * avPacket = av_packet_alloc();
        int ret = av_read_frame(avFormatContext, avPacket);
        if (ret < 0 ) {
            LogI("read packet over");
            isStart = false;
            videoQueue->clearAVPacket();
            audioQueue->clearAVPacket();
            break;
        }
        if (avPacket->stream_index == videoIndex) {
            videoQueue->push(avPacket);
            LogD("vPacket size %d", avPacket->size);
        } else if (avPacket->stream_index == audioIndex) {
            audioQueue->push(avPacket);
            LogD("aPacket size %d", avPacket->size);
        }

    }
    avformat_close_input(&avFormatContext);
    LogI("----------decode packet thread end");
    return NULL;
}

void *decodeVideo(void *pVoid) {
    LogI("----------decode video thread");

    while (isStart) {
        AVPacket *videoPacket = av_packet_alloc();
        videoQueue->getAVPacket(videoPacket);
        int ret = avcodec_send_packet(vCodecContext, videoPacket);
        AVFrame *vFrame = av_frame_alloc();
        if (ret != 0) {
            av_frame_free(&vFrame);
            av_free(vFrame);
            vFrame = NULL;
            av_packet_free(&videoPacket);
            av_free(videoPacket);
            videoPacket = NULL;
            LogI("send vPacket error");
            break;
        }
        ret = avcodec_receive_frame(vCodecContext, vFrame);
        LogD("vFrame %d", vFrame->linesize[0]);
        if (ret != 0) {
            av_frame_free(&vFrame);
            av_free(vFrame);
            vFrame = NULL;
            av_packet_free(&videoPacket);
            av_free(videoPacket);
            videoPacket = NULL;
            LogI("----------skip vF");
            continue;
        }

        sws_scale(swsContext, vFrame->data, vFrame->linesize, 0, vCodecContext->height,
                  rgbFrame->data, rgbFrame->linesize);
        ANativeWindow_lock(nativeWindow, &windowBuffer, NULL);
        uint8_t *dstWindow = static_cast<uint8_t *>(windowBuffer.bits);
        for (int i = 0; i < height; ++i) {
            memcpy(dstWindow + i * windowBuffer.stride*4,
                   outBuffer + i * rgbFrame->linesize[0],
                   rgbFrame->linesize[0]);
        }
        ANativeWindow_unlockAndPost(nativeWindow);
        //double time = 40 * 1000;
        //av_usleep(time);
        av_frame_free(&vFrame);
        vFrame = NULL;
        av_packet_free(&videoPacket);
        av_free(videoPacket);
        videoPacket = NULL;
    }
    avcodec_close(vCodecContext);
    LogI("----------decode video thread end");
    return NULL;
}

void *decodeAudio(void *pVoid) {
    LogI("----------decode audio thread");
    SwrContext * swrContext = swr_alloc();
    swr_alloc_set_opts(swrContext, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16,
                       44100, aCodecContext->channel_layout,
                       aCodecContext->sample_fmt, aCodecContext->sample_rate, 0,
                       NULL);
    swr_init(swrContext);

    uint8_t *outBuffer = (uint8_t *) av_malloc(44100 * 2);
    int out_channel_nb = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
    JNIEnv *jniEnv;

    while (isStart) {
        AVPacket *audioPacket = av_packet_alloc();
        audioQueue->getAVPacket(audioPacket);
        AVFrame *aFrame = av_frame_alloc();
        int ret = avcodec_send_packet(aCodecContext, audioPacket);
//        if (ret != 0) {
//            av_frame_free(&aFrame);
//            av_free(aFrame);
//            aFrame = NULL;
//            av_packet_free(&audioPacket);
//            av_free(audioPacket);
//            audioPacket = NULL;
//            LogI("send apacket error");
//            break;
//        }
        ret = avcodec_receive_frame(aCodecContext, aFrame);
        if (ret != 0) {
            av_frame_free(&aFrame);
            av_free(aFrame);
            aFrame = NULL;
            av_packet_free(&audioPacket);
            av_free(audioPacket);
            audioPacket = NULL;
            LogI("----------skip aF");
            continue;
        }
        swr_convert(swrContext, &outBuffer, 44100 * 2,
                    (const uint8_t **) (aFrame->data), aFrame->nb_samples);

        int size = av_samples_get_buffer_size(NULL, out_channel_nb, aFrame->nb_samples,
                                              AV_SAMPLE_FMT_S16, 1);

        LogD("pcm size %d", size);
        if (javaVm->AttachCurrentThread(&jniEnv, 0) != JNI_OK) {
            LogE("can't attach");
            continue;
        }
        jbyteArray byteArray = jniEnv->NewByteArray(size);
        jniEnv->SetByteArrayRegion(byteArray, 0, size,
                                   reinterpret_cast<const jbyte *>(outBuffer));
        jniEnv->CallVoidMethod(mInstance, playTrack, byteArray, size);
        javaVm->DetachCurrentThread();
        av_frame_free(&aFrame);
    }
    swr_free(&swrContext);
    avcodec_close(aCodecContext);
    jniEnv->DeleteGlobalRef(mInstance);
    LogI("----------decode audio thread end");
    return NULL;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_rocklee_rockplayer_RockPlayer_play(JNIEnv *env, jobject instance, jstring url_,
                                            jobject surface) {
    // TODO: implement play()
    const char *url = env->GetStringUTFChars(url_, 0);

    //network init
    //avformat_network_init();

    avFormatContext = avformat_alloc_context();
    avformat_open_input(&avFormatContext, url, NULL, NULL);
    avformat_find_stream_info(avFormatContext, NULL);

    for (int i = 0; i < avFormatContext->nb_streams; i++) {
        if (avFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoIndex = i;
            //get codec para
            AVCodecParameters *vCodecParameters = avFormatContext->streams[i]->codecpar;
            //get decoder
            AVCodec *vCodec = avcodec_find_decoder(vCodecParameters->codec_id);
            //init codec context
            vCodecContext = avcodec_alloc_context3(vCodec);
            //put para to context
            avcodec_parameters_to_context(vCodecContext, vCodecParameters);
            LogI("video%d, id %d", i, vCodecParameters->codec_id);
            //start
            avcodec_open2(vCodecContext, vCodec, 0);

        } else if (avFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            audioIndex = i;
            AVCodecParameters *aCodecParameters = avFormatContext->streams[i]->codecpar;
            AVCodec *aCodec = avcodec_find_decoder(aCodecParameters->codec_id);
            aCodecContext = avcodec_alloc_context3(aCodec);
            avcodec_parameters_to_context(aCodecContext, aCodecParameters);
            LogI("audio%d， id %d", i, aCodecParameters->codec_id);
            int ret = avcodec_open2(aCodecContext, aCodec, 0);
            if (ret < 0) {
                LogE("open audio decoder error");
                return;
            }
        }
    }
    mInstance = env->NewGlobalRef(instance);

    width = vCodecContext->width;
    height = vCodecContext->height;

    nativeWindow = ANativeWindow_fromSurface(env, surface);
    ANativeWindow_setBuffersGeometry(nativeWindow, width, height, WINDOW_FORMAT_RGBA_8888);

    jclass rockPlayer = env->GetObjectClass(instance);
    onSizeChange = env->GetMethodID(rockPlayer, "onSizeChange", "(II)V");
    env->CallVoidMethod(instance, onSizeChange, width, height);

    jmethodID createTrack = env->GetMethodID(rockPlayer, "createTrack", "(II)V");
    env->CallVoidMethod(instance, createTrack, 44100,
                        av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO));
    playTrack = env->GetMethodID(rockPlayer, "playTrack", "([BI)V");

    int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGBA, width, height, 1);
    outBuffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
    rgbFrame = av_frame_alloc();
    av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, outBuffer,
                         AV_PIX_FMT_RGBA, width, height, 1);

    swsContext = sws_getContext(width, height, vCodecContext->pix_fmt, width, height,
                   AV_PIX_FMT_RGBA, SWS_BICUBIC, NULL, NULL, NULL);

    isStart = true;
    videoQueue = new DataQueue;
    audioQueue = new DataQueue;
    pthread_t thread_decode;
    pthread_t thread_video;
    pthread_t thread_audio;
    pthread_create(&thread_decode, NULL, decodePacket, NULL);
    pthread_create(&thread_video, NULL, decodeVideo, NULL);
    pthread_create(&thread_audio, NULL, decodeAudio, NULL);

    env->ReleaseStringUTFChars(url_, url);
}